/****************************************************************************
 * Visual Boy Advance GX
 *
 * Tantric September 2008
 *
 * filebrowser.cpp
 *
 * Generic file routines - reading, writing, browsing
 ***************************************************************************/

#include <gccore.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <wiiuse/wpad.h>
#include <sys/dir.h>
#include <malloc.h>
#include <di/di.h>

#include "wiids.h"
#include "filebrowser.h"
#include "menu.h"
#include "video.h"
#include "fileop.h"
#include "input.h"

BROWSERINFO browser;
BROWSERENTRY * browserList = NULL; // list of files/folders in browser

char ROMFilename[512];
bool ROMLoaded = false;
//bool loadingFile = false;

/****************************************************************************
* autoLoadMethod()
* Auto-determines and sets the load device
* Returns device set
****************************************************************************/
int autoLoadMethod()
{
	ShowAction ("Attempting to determine load device...");

	int device = DEVICE_AUTO;

	if(ChangeInterface(DEVICE_SD, SILENT))
		device = DEVICE_SD;
	else if(ChangeInterface(DEVICE_USB, SILENT))
		device = DEVICE_USB;
	else if(ChangeInterface(DEVICE_DVD, SILENT))
		device = DEVICE_DVD;
	else
		ErrorPrompt("Unable to locate a load device!");

	if(Settings.LoadMethod == DEVICE_AUTO)
		Settings.LoadMethod = device; // save device found for later use
	CancelAction();
	return device;
}

/****************************************************************************
* autoSaveMethod()
* Auto-determines and sets the save device
* Returns device set
****************************************************************************/
int autoSaveMethod(bool silent)
{
	if(!silent)
		ShowAction ("Attempting to determine save device...");

	int device = DEVICE_AUTO;

	if(ChangeInterface(DEVICE_SD, SILENT))
		device = DEVICE_SD;
	else if(ChangeInterface(DEVICE_USB, SILENT))
		device = DEVICE_USB;
	else if(!silent)
		ErrorPrompt("Unable to locate a save device!");

	if(Settings.SaveMethod == DEVICE_AUTO)
		Settings.SaveMethod = device; // save device found for later use

	CancelAction();
	return device;
}

/****************************************************************************
 * ResetBrowser()
 * Clears the file browser memory, and allocates one initial entry
 ***************************************************************************/
void ResetBrowser()
{
	browser.numEntries = 0;
	browser.selIndex = 0;
	browser.pageIndex = 0;

	// Clear any existing values
	if(browserList != NULL)
	{
		free(browserList);
		browserList = NULL;
	}
	// set aside space for 1 entry
	browserList = (BROWSERENTRY *)malloc(sizeof(BROWSERENTRY));
	memset(browserList, 0, sizeof(BROWSERENTRY));
	browser.size = 1;
}

bool AddBrowserEntry()
{
	BROWSERENTRY * newBrowserList = (BROWSERENTRY *)realloc(browserList, (browser.size+1) * sizeof(BROWSERENTRY));

	if(!newBrowserList) // failed to allocate required memory
	{
		ResetBrowser();
		ErrorPrompt("Out of memory: too many files!");
		return false;
	}
	else
	{
		browserList = newBrowserList;
	}
	memset(&(browserList[browser.size]), 0, sizeof(BROWSERENTRY)); // clear the new entry
	browser.size++;
	return true;
}

/****************************************************************************
 * CleanupPath()
 * Cleans up the filepath, removing double // and replacing \ with /
 ***************************************************************************/
static void CleanupPath(char * path)
{
	if(!path || path[0] == 0)
		return;
	
	int pathlen = strlen(path);
	int j = 0;
	for(int i=0; i < pathlen && i < MAXPATHLEN; i++)
	{
		if(path[i] == '\\')
			path[i] = '/';

		if(j == 0 || !(path[j-1] == '/' && path[i] == '/'))
			path[j++] = path[i];
	}
	path[j] = 0;
}

bool IsDeviceRoot(char * path)
{
	if(path == NULL || path[0] == 0)
		return false;

	if(strcmp(path, "sd:/") == 0 ||
		strcmp(path, "usb:/") == 0 ||
		strcmp(path, "dvd:/") == 0)
	{
		return true;
	}
	return false;
}

/****************************************************************************
 * UpdateDirName()
 * Update curent directory name for file browser
 ***************************************************************************/
int UpdateDirName()
{
	int size=0;
	char * test;
	char temp[1024];
	int device = 0;

	if(browser.numEntries == 0)
		return 1;

	FindDevice(browser.dir, &device);

	/* current directory doesn't change */
	if (strcmp(browserList[browser.selIndex].filename,".") == 0)
	{
		return 0;
	}
	/* go up to parent directory */
	else if (strcmp(browserList[browser.selIndex].filename,"..") == 0)
	{
		// already at the top level
		if(IsDeviceRoot(browser.dir))
		{
			browser.dir[0] = 0; // remove device - we are going to the device listing screen
		}
		else
		{
			/* determine last subdirectory namelength */
			sprintf(temp,"%s",browser.dir);
			test = strtok(temp,"/");
			while (test != NULL)
			{
				size = strlen(test);
				test = strtok(NULL,"/");
			}
	
			/* remove last subdirectory name */
			size = strlen(browser.dir) - size - 1;
			browser.dir[size] = 0;
		}

		return 1;
	}
	/* Open a directory */
	else
	{
		/* test new directory namelength */
		if ((strlen(browser.dir)+1+strlen(browserList[browser.selIndex].filename)) < MAXPATHLEN)
		{
			/* update current directory name */
			sprintf(browser.dir, "%s%s/",browser.dir, browserList[browser.selIndex].filename);
			return 1;
		}
		else
		{
			ErrorPrompt("Directory name is too long!");
			return -1;
		}
	}
}

bool MakeFilePath(char filepath[], int type, char * filename, int filenum)
{
	char file[512];
	char folder[1024];
	char ext[4];
	char temppath[MAXPATHLEN];

	if(type == FILE_ROM)
	{
		// Check path length
		if ((strlen(browser.dir)+1+strlen(browserList[browser.selIndex].filename)) >= MAXPATHLEN)
		{
			ErrorPrompt("Maximum filepath length reached!");
			filepath[0] = 0;
			return false;
		}
		else
		{
			sprintf(temppath, "%s%s",browser.dir,browserList[browser.selIndex].filename);
		}
	}
	else
	{
		if(Settings.SaveMethod == DEVICE_AUTO)
			Settings.SaveMethod = autoSaveMethod(SILENT);

		if(Settings.SaveMethod == DEVICE_AUTO)
			return false;

		switch(type)
		{
			case FILE_FLASH:
			case FILE_EEPROM:
			case FILE_SNAPSHOT:
				sprintf(folder, Settings.SaveFolder);

				if(type == FILE_FLASH || type == FILE_EEPROM) sprintf(ext, "sav");
				else sprintf(ext, "sgm");

				if(filenum >= -1)
				{
					if(filenum == -1)
						sprintf(file, "%s.%s", filename, ext);
					else if(filenum == 0)
						sprintf(file, "%s Auto.%s", filename, ext);
					else
						sprintf(file, "%s %i.%s", filename, filenum, ext);
				}
				else
				{
					sprintf(file, "%s", filename);
				}
				break;
		}
		sprintf (temppath, "%s%s/%s", pathPrefix[Settings.SaveMethod], folder, file);
	}
	CleanupPath(temppath); // cleanup path
	strncpy(filepath, temppath, MAXPATHLEN);
	return true;
}

/****************************************************************************
 * FileSortCallback
 *
 * Quick sort callback to sort file entries with the following order:
 *   .
 *   ..
 *   <dirs>
 *   <files>
 ***************************************************************************/
int FileSortCallback(const void *f1, const void *f2)
{
	/* Special case for implicit directories */
	if(((BROWSERENTRY *)f1)->filename[0] == '.' || ((BROWSERENTRY *)f2)->filename[0] == '.')
	{
		if(strcmp(((BROWSERENTRY *)f1)->filename, ".") == 0) { return -1; }
		if(strcmp(((BROWSERENTRY *)f2)->filename, ".") == 0) { return 1; }
		if(strcmp(((BROWSERENTRY *)f1)->filename, "..") == 0) { return -1; }
		if(strcmp(((BROWSERENTRY *)f2)->filename, "..") == 0) { return 1; }
	}

	/* If one is a file and one is a directory the directory is first. */
	if(((BROWSERENTRY *)f1)->isdir && !(((BROWSERENTRY *)f2)->isdir)) return -1;
	if(!(((BROWSERENTRY *)f1)->isdir) && ((BROWSERENTRY *)f2)->isdir) return 1;

	return stricmp(((BROWSERENTRY *)f1)->filename, ((BROWSERENTRY *)f2)->filename);
}

/****************************************************************************
 * ShortenFilename
 *
 * Removes garbage and strips the extension from a filename
 ***************************************************************************/
void ShortenFilename(char * returnstring, char * inputstring)
{
	if (!inputstring) {
		returnstring[0] = '\0';
		return;
	}
	// skip file extension
	char * loc_dot = (char *)strrchr(inputstring,'.');
	char * s = (char *)inputstring;
	char * r = (char *)returnstring;
	// skip initial whitespace, numbers, - and _ ...
	while ((*s!='\0' && *s<=' ') || *s=='-' || *s=='_' || *s=='+') s++;
	// ... except those that SHOULD begin with numbers
	if (strncmp(s,"007",3)==0) for (int i=0; i<3; i++, r++, s++) *r=*s;
	if (strncmp(s,"1 Contre 100",12)==0) for (int i=0; i<12; i++, r++, s++) *r=*s;
	if (strncmp(s,"1 Gegen 100",11)==0) for (int i=0; i<11; i++, r++, s++) *r=*s;
	if (strncmp(s,"1 vs 100",8)==0) for (int i=0; i<8; i++, r++, s++) *r=*s;
	if (strncmp(s,"10 Voor",7)==0) for (int i=0; i<7; i++, r++, s++) *r=*s;
	if (strncmp(s,"100 All",7)==0) for (int i=0; i<7; i++, r++, s++) *r=*s;
	if (strncmp(s,"100 Classic",11)==0) for (int i=0; i<11; i++, r++, s++) *r=*s;
	if (strncmp(s,"100 Livres",10)==0) for (int i=0; i<10; i++, r++, s++) *r=*s;
	if (strncmp(s,"1000 Bornes",11)==0) for (int i=0; i<11; i++, r++, s++) *r=*s;
	if (strncmp(s,"101 Dino",8)==0) for (int i=0; i<8; i++, r++, s++) *r=*s;
	if (strncmp(s,"101 In 1",8)==0) for (int i=0; i<8; i++, r++, s++) *r=*s;
	if (strncmp(s,"101-In-1",8)==0) for (int i=0; i<8; i++, r++, s++) *r=*s;
	if (strncmp(s,"1500 DS",7)==0) for (int i=0; i<7; i++, r++, s++) *r=*s;
	if (strncmp(s,"2 in 1",6)==0) for (int i=0; i<6; i++, r++, s++) *r=*s;
	if (strncmp(s,"2-in-1",6)==0) for (int i=0; i<6; i++, r++, s++) *r=*s;
	if (strncmp(s,"200 Klassische",14)==0) for (int i=0; i<14; i++, r++, s++) *r=*s;
	if (strncmp(s,"3 in 1",6)==0) for (int i=0; i<6; i++, r++, s++) *r=*s;
	if (strncmp(s,"3-in-1",6)==0) for (int i=0; i<6; i++, r++, s++) *r=*s;
	if (strncmp(s,"4 in 1",6)==0) for (int i=0; i<6; i++, r++, s++) *r=*s;
	if (strncmp(s,"4-in-1",6)==0) for (int i=0; i<6; i++, r++, s++) *r=*s;
	if (strncmp(s,"42 All",6)==0) for (int i=0; i<6; i++, r++, s++) *r=*s;
	if (strncmp(s,"50 Classic",10)==0) for (int i=0; i<10; i++, r++, s++) *r=*s;
	if (strncmp(s,"7 Wonders",9)==0) for (int i=0; i<9; i++, r++, s++) *r=*s;
	if (strncmp(s,"7th",3)==0) for (int i=0; i<3; i++, r++, s++) *r=*s;
	while (*s>='0' && *s<='9') s++;
	if (r==(char *)returnstring) while ((*s!='\0' && *s<=' ') || *s=='-' || *s=='_' || *s=='+') s++;
	// now go through rest of string until we get to the extension
	while (*s!='\0' && (loc_dot==NULL || s<loc_dot)) {
		// skip all but 1 '-', '_' or space in a row
		char c = s[0];
		if (c==s[1] && (c=='-' || c=='_' || c==' ')) s++;
		// skip space before hyphen
		if (*s==' ' && s[1]=='-') s++;
		// copy character to result
		if (*s=='_') *r=' ';
		else *r = *s;
		// skip spaces after hyphen
		if (*s=='-') while (s[1]==' ') s++;
		s++; r++;
	}
	// strip file extension
	if (loc_dot != NULL) *loc_dot = 0;
}

/****************************************************************************
 * BrowserLoadFile
 *
 * Loads the selected ROM
 ***************************************************************************/
//int BrowserLoadFile()
//{
//	int device;

//	if(!FindDevice(browser.dir, &device))
//		return 0;

//	strcpy(ROMFilename, browserList[browser.selIndex].filename);

//	loadingFile = true;
//	ROMLoaded = NDS_LoadROM(ROMFilename, cflash_disk_image_file);
//	loadingFile = false;

//	if (!ROMLoaded)
//	{
//		ErrorPrompt("Error loading ROM!");
//	}
//	else
//	{
//		if (Settings.AutoLoad == 1)
//			LoadBatteryOrStateAuto(FILE_FLASH, SILENT);
//		else if (Settings.AutoLoad == 2)
//			LoadBatteryOrStateAuto(FILE_EEPROM, SILENT);
//		else if (Settings.AutoLoad == 3)
//			LoadBatteryOrStateAuto(FILE_SNAPSHOT, SILENT);
//		ResetBrowser();
//	}
//	CancelAction();
//	return ROMLoaded;
//}

/****************************************************************************
 * BrowserChangeFolder
 *
 * Update current directory and set new entry list if directory has changed
 ***************************************************************************/
int BrowserChangeFolder()
{
	int device = 0;
	FindDevice(browser.dir, &device);

	if(!UpdateDirName())
		return -1;

	CleanupPath(browser.dir);
	HaltParseThread(); // halt parsing
	ResetBrowser(); // reset browser

	if(browser.dir[0] != 0)
		ParseDirectory();

	if(browser.numEntries == 0)
	{
		browser.dir[0] = 0;
		int i=0;
		
		AddBrowserEntry();
		sprintf(browserList[i].filename, "sd:/");
		sprintf(browserList[i].displayname, "SD Card");
		browserList[i].length = 0;
		browserList[i].mtime = 0;
		browserList[i].isdir = 1;
		browserList[i].icon = ICON_SD;
		i++;

		AddBrowserEntry();
		sprintf(browserList[i].filename, "usb:/");
		sprintf(browserList[i].displayname, "USB Mass Storage");
		browserList[i].length = 0;
		browserList[i].mtime = 0;
		browserList[i].isdir = 1;
		browserList[i].icon = ICON_USB;
		i++;

		AddBrowserEntry();
		sprintf(browserList[i].filename, "dvd:/");
		sprintf(browserList[i].displayname, "Data DVD");
		browserList[i].length = 0;
		browserList[i].mtime = 0;
		browserList[i].isdir = 1;
		browserList[i].icon = ICON_DVD;
		i++;
		
		browser.numEntries += i;
	}
	
	if(browser.dir[0] == 0)
	{
		Settings.LoadFolder[0] = 0;
		Settings.LoadMethod = 0;
	}
	else
	{
		char * path = StripDevice(browser.dir);
		if(path != NULL)
			strcpy(Settings.LoadFolder, path);
		FindDevice(browser.dir, &Settings.LoadMethod);
	}

	return browser.numEntries;
}

/****************************************************************************
 * OpenROM
 * Displays a list of ROMS on load device
 ***************************************************************************/
int
OpenGameList ()
{
	int device = Settings.LoadMethod;

	if(device == DEVICE_AUTO && strlen(Settings.LoadFolder) > 0)
		device = autoLoadMethod();

	// change current dir to roms directory
	if(device > 0)
		sprintf(browser.dir, "%s%s/", pathPrefix[device], Settings.LoadFolder);
	else
		browser.dir[0] = 0;
	
	BrowserChangeFolder();
	return browser.numEntries;
}
